<?php

namespace App\Models\Orders\Repositories;

use App\Exceptions\NotRealizeInstanceException;
use App\Exceptions\TooManyActionException;
use App\Models\Orders\Orders;
use App\Models\Orders\Repositories\Interfaces\OrdersInterface;
use App\Models\Orders\Repositories\Interfaces\OrdersRepositoryInterface;
use App\Models\Plane\PlaneOrders\PlaneOrder;
use App\Models\Plane\PlaneOrders\Repositories\Interfaces\PlaneOrderRepositoryInterface;
use App\Models\Train\TrainOrders\Repositories\Interfaces\TrainOrdersRepositoryInterface;
use App\Models\Train\TrainOrders\TrainOrder;
use App\Trip\Orders\TripNo;
use App\Trip\Orders\TripOrderState;
use App\Trip\Orders\TripType;
use App\Trip\Repository\BaseRepository;
use Carbon\Carbon;
use Exception;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Throwable;

class OrdersRepository extends BaseRepository implements OrdersRepositoryInterface
{

    public function __construct(Orders $model)
    {
        parent::__construct($model);
    }

    /**
     * @throws Exception
     */
    public function lists(array $param, int $page_size): Collection
    {
        $total  = $this->listCommonQuery($param)
                       ->count();
        $isSort = isset($param['sort_key']) && isset($param['sort_value']) && in_array($param['sort_value'], [1, 2]);
        /**
         * @var Paginator $listData
         */
        $listData = $this->listCommonQuery($param)
                         ->with('train_passengers', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'truename',
                                 'ticket_price',
                                 'seat_name',
                                 'seat_name_uy',
                                 'seat_no',
                             ]);
                         })
                         ->with('train_order', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'task_id',
                                 'snap_up',
                                 'order_snap_state',
                             ]);
                         })
                         ->with('train_info', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'train_code',
                                 'from_station_code',
                                 'to_station_code',
                                 'from_station_name',
                                 'from_station_name_uy',
                                 'to_station_name',
                                 'to_station_name_uy',
                                 'start_at',
                                 'arrive_at',
                                 'day_difference',
                                 'run_time',
                             ]);
                         })
                         ->with('plane_passengers', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'truename',
                                 'ticket_price',
                             ]);
                         })
                         ->with('plane_order', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'provider_price_state',
                             ]);
                         })
                         ->with('flight_info', function ($q) {
                             return $q->select([
                                 'order_sn',
                                 'flight_no',
                                 'from_code',
                                 'to_code',
                                 'from_name',
                                 'from_name_uy',
                                 'to_name',
                                 'to_name_uy',
                                 'start_at',
                                 'arrive_at',
                                 'day_difference',
                                 'run_time',
                             ]);
                         })
                         ->when($isSort, function ($query) use ($param) {
                             $sort = [
                                 1 => "ASC",
                                 2 => "DESC",
                             ];
                             if ($param['sort_key'] == 'start_at') {
                                 $query->orderBy('trip_at', $sort[$param['sort_value']]);
                             }

                             if ($param['sort_key'] == 'order_at') {
                                 $query->orderBy('created_at', $sort[$param['sort_value']]);
                             }
                         })
                         ->orderByDesc('updated_at')
                         ->orderByDesc('id')
                         ->simplePaginate($page_size);


        $page_size    = $page_size == 0 ? 1 : $page_size;
        $last_page    = ceil($total / $page_size) ?: 1;
        $current_page = $listData->currentPage();
        $orders       = $listData->items();

        return collect(compact('orders', 'total', 'last_page', 'current_page'));
    }

    /**
     * @throws Exception
     */
    private function listCommonQuery(array $param)
    {
        return $this->model
            ->when(isset($param['user_id']), function ($query) use ($param) {
                $query->where('user_id', $param['user_id']);
            })
            ->when(isset($param['trip_id']), function ($query) use ($param) {
                $query->where('trip_id', $param['trip_id']);
            })
            ->where(function ($query) use ($param) {
                if (isset($param['index_query']) && $param['index_query'] == 1) {
                    $query->whereIn('order_state', [
                        TripOrderState::ORDER_STATE_WAITING_PAY,
                        TripOrderState::ORDER_STATE_TICKETED,
                        TripOrderState::ORDER_STATE_CHANGED,
                    ])
                          ->where('trip_at', '>=', Carbon::now()->toDateTimeString());
                }
                if (isset($param['is_valid']) && $param['is_valid'] == 1) {
                    $query->whereIn('order_state', TripOrderState::valid_states());
                }

                if (isset($param['trip_type']) && $param['trip_type']) {
                    $query->where('trip_type', $param['trip_type']);
                }
            })
            ->where('delete_state', TripOrderState::DELETE_STATE_NOT_DELETE);
    }

    public function createOrUpdateOrder(OrdersInterface $order, array $data): Orders
    {
        // 按照 trip_no 和 trip_id 查询
        if ($commonOrder = $this->findOneBy([
            'trip_no' => $order->getTripNo(),
            'trip_id' => $order->getTripId(),
            'user_id' => $order->getUserId(),
        ])
        ) {
            $updateData = [
                'order_sn'     => $order->getOrderSn(),
                'order_state'  => $data['order_state'] ?? $order->getOrderState(),
                'trip_at'      => $order->getTripDate(),
                'order_amount' => $order->getOrderAmount(),
            ];
            if (isset($data['updated_at'])) {
                $updateData['updated_at'] = $data['updated_at'];
            }

            $this->updateOrder($commonOrder, $updateData);
        } else {
            $commonOrder = $this->create([
                'channel_id'    => $order->getChannelId(),
                'trip_no'       => $order->getTripNo(),
                'trip_id'       => $order->getTripId(),
                'user_id'       => $order->getUserId(),
                'order_sn'      => $order->getOrderSn(),
                'trip_type'     => $order->getTripType(),
                'transfer_flag' => $order->getTransferFlag(),
                'pay_sn'        => '',
                'order_state'   => $order->getOrderState(),
                'trip_at'       => $order->getTripDate(),
                'order_amount'  => $order->getOrderAmount(),
                'created_at'    => $order->getCreatedAt(),
            ]);
        }
        return $commonOrder;
    }

    public function updateOrder(Orders $order, array $data): bool
    {
        return $this->update($data, $order);
    }

    /**
     * 删除订单
     *
     * @param Orders $order
     *
     * @return bool
     * @throws Exception
     * @throws Throwable
     */
    public function destroy(Orders $order): bool
    {
        $deleteCacheKey = "Trip:OrderDelete:{$order->getOrderSn()}";
        if (Cache::has($deleteCacheKey)) {
            throw new TooManyActionException("您操作的太快了，请稍后重试");
        }

        Cache::put($deleteCacheKey, 1, 10);
        if ($order->isDelete()) {
            return true;
        }

        if ($order->canDelete() != 1) {
            throw new Exception("该订单不可删除");
        }


        if ($order->isTrainOrder()) {
            $trainOrderRepo = app(TrainOrdersRepositoryInterface::class);
            $trainOrder     = $trainOrderRepo->getOrderInfoByOrder($order);
            DB::transaction(function () use ($order, $trainOrder) {
                $deleted_at = Carbon::now()->toDateTimeString();
                $this->updateOrder($order, [
                    'delete_state' => 1,
                    'deleted_at'   => $deleted_at,
                ]);

                $trainOrder->update([
                    'delete_state' => 1,
                    'deleted_at'   => $deleted_at,
                ]);
            });
        } else if ($order->isPlaneOrder()) {
            $planeOrderRepo = app(PlaneOrderRepositoryInterface::class);
            $planeOrder     = $planeOrderRepo->getOrderInfoByOrder($order);
            DB::transaction(function () use ($order, $planeOrder) {
                $deleted_at = Carbon::now()->toDateTimeString();
                $this->updateOrder($order, [
                    'delete_state' => 1,
                    'deleted_at'   => $deleted_at,
                ]);

                $planeOrder->update([
                    'delete_state' => 1,
                    'deleted_at'   => $deleted_at,
                ]);
            });
        } else {
            throw new NotRealizeInstanceException("其他订单类型删除未实现");
        }

        Cache::forget($deleteCacheKey);
        return true;

    }

    public function findByTrainOrder(TrainOrder $order): Orders
    {
        return $this->findOneBy([
            'order_sn'  => $order->getOrderSn(),
            'trip_id'   => $order->getTripId(),
            'user_id'   => $order->getUserId(),
            'trip_type' => TripType::TRIP_TYPE_TRAIN,
        ]);
    }

    // 主要用于,仅更新状态 不更新 当前订单时查询

    public function findByPlaneOrder(PlaneOrder $order): Orders
    {
        return $this->findOneBy([
            'order_sn'  => $order->getOrderSn(),
            'trip_id'   => $order->getTripId(),
            'user_id'   => $order->getUserId(),
            'trip_type' => TripType::TRIP_TYPE_PLANE,
        ]);
    }

    public function findOneByTrainOrderTrip(TrainOrder $order): Orders
    {
        return $this->findOneBy([
            'trip_id'   => $order->getTripId(),
            'trip_no'   => $order->getTripNo(),
            'user_id'   => $order->getUserId(),
            'trip_type' => TripType::TRIP_TYPE_TRAIN,
        ]);
    }

    /**
     * 获取中转详情
     *
     * @throws Exception
     */
    public function getTransferInfo(Orders $order): array
    {
        /**
         * @var Orders                         $trip
         * @var TrainOrdersRepositoryInterface $trainOrderRepo
         */
        $trainOrderRepo      = app(TrainOrdersRepositoryInterface::class);
        $trips               = $this->listByTrainOrder($trainOrderRepo->getOrderInfo($order->getOrderSn()));
        $order_amount        = 0;
        $order_info          = [];
        $orders              = [];
        $transfer_time1      = null;
        $transfer_time2      = null;
        $transfer_type       = "市内换乘";
        $firstEndStationName = "";
        $price_details       = $order->getPriceDetails();
        $max_pay_at          = "";
        foreach ($trips as $trip) {
            $order_amount += $trip['order_amount'];
            if ($trip->getTripNo() == TripNo::TRIP_NO_FIRST && $trip->isTrainOrder()) {
                $firstOrder                 = $trainOrderRepo->getOrderShowInfo($trip->getOrderSn());
                $train_info                 = $firstOrder['train_info'];
                $order_info["from_name"]    = $train_info['from_station_name'];
                $order_info["from_name_uy"] = $train_info['from_station_name_uy'];
                $firstEndStationName        = $train_info['to_station_name'];
                $transfer_time1             = Carbon::parse($train_info['arrive_at'])->format('Y-m-d H:i');
                $max_pay_at                 = $firstOrder['max_pay_at'];
                unset($firstOrder['trip_costs'], $firstOrder['other_fee']);
                $orders[] = $firstOrder;
            }
            if ($trip->getTripNo() == TripNo::TRIP_NO_SECOND && $trip->isTrainOrder()) {
                $secondOrder              = $trainOrderRepo->getOrderShowInfo($trip->getOrderSn());
                $train_info               = $secondOrder['train_info'];
                $order_info["to_name"]    = $train_info['to_station_name'];
                $order_info["to_name_uy"] = $train_info['to_station_name_uy'];
                $transfer_time2           = Carbon::parse($train_info['start_at'])->format('Y-m-d H:i');
                unset($secondOrder['trip_costs'], $secondOrder['other_fee']);
                $orders[] = $secondOrder;
                if ($firstEndStationName == $train_info['from_station_name']) {
                    $transfer_type = "同站换乘";
                }
            }
        }
        $order_info['order_amount']     = $order_amount;
        $order_info['order_state']      = $order->getOrderState();
        $order_info['order_state_desc'] = "";
        $order_info['max_pay_at']       = $max_pay_at;
        $order_info['transfer']         = [
            "transfer_minutes" => Carbon::parse($transfer_time2)->diffInMinutes($transfer_time1),
            "type"             => [
                "name"    => $transfer_type,
                "name_uy" => trans2uy($transfer_type),
            ],
        ];
        $order_info['orders']           = $orders;
        return compact('order_info', 'price_details');
    }

    /**
     * @throws Exception
     */
    public function listByTrainOrder(TrainOrder $order): Collection
    {
        $param['trip_id'] = $order->getTripId();
        $param['user_id'] = $order->getUserId();
        return $this->listCommonQuery($param)->get();
    }

    public function getOrderInfo(string $order_sn): ?Orders
    {
        return $this->findOneBy([
            'order_sn' => $order_sn,
        ]);
    }

    /**
     * @inheritDoc
     * @throws Exception
     */
    public function listByTripId(string $trip_id): Collection
    {
        $param['trip_id'] = $trip_id;
        return $this->listCommonQuery($param)->get();
    }

    /**
     * @param PlaneOrder $order
     * @return Orders
     */
    public function findOneByPlaneOrderTrip(PlaneOrder $order): Orders
    {
        return $this->findOneBy([
            'trip_id'   => $order->getTripId(),
            'trip_no'   => $order->getTripNo(),
            'user_id'   => $order->getUserId(),
            'trip_type' => TripType::TRIP_TYPE_PLANE,
        ]);
    }

    public function lastOrder(string $user_id): ?Orders
    {
        return $this->model->where(['user_id' => $user_id])
                           ->where('order_state', '>', 0)
                           ->orderByDesc('id')
                           ->first();
    }
}